/*
 * Frida 脚本，通过 Hook dlopen 来查找 RegisterNatives (终极方案)。
 * 策略: Hook android_dlopen_ext，在目标库加载后，在其内部搜索 RegisterNatives 符号。
 *       这可以绕过全局搜索失败的问题。
 * 
 * 使用方法: frida -U -f com.zhiliaoapp.musically -l hook_dlopen_for_jni.js --no-pause
 */

console.log("[*] 通过 Hook dlopen 查找 JNI 函数的脚本已启动。");

// 我们关心的目标库，可以根据需要添加或修改
const targetLibraries = [
    "libart.so",      // 系统核心库，通常 RegisterNatives 在这里
    "libworker.so",   // JS Worker，我们的重点怀疑对象
    "libsscronet.so", // TTNet 网络库
    "libturing.so",   // 风控库
    "libmetasec_ml.so"// 另一个安全相关的库
];

// 用于过滤 JNI 注册的类名关键字
const filterKeywords = [
    "tiktok", "bytedance", "musically", "ss.android", "captcha", 
    "rizon", "turing", "sec", "libtt", "ies", "toutiao", "worker"
];

// 一个标记，防止我们重复 Hook RegisterNatives
let is_jni_hooked = false;

// 获取 android_dlopen_ext 函数的地址，这是安卓加载 so 库的标准函数
const dlopen_ext_addr = Module.findExportByName(null, "android_dlopen_ext");

if (dlopen_ext_addr) {
    console.log("[*] 已定位 android_dlopen_ext 地址: " + dlopen_ext_addr);
    Interceptor.attach(dlopen_ext_addr, {
        onEnter: function(args) {
            // arg[0] 是被加载的 so 库的路径
            const library_path = args[0].readCString();
            if (library_path) {
                // 检查被加载的库是否是我们的目标之一
                const library_name = library_path.split('/').pop();
                if (targetLibraries.includes(library_name)) {
                    console.log(`[+] 检测到目标库正在被加载: ${library_name}`);
                    this.library_name = library_name; // 将库名保存在 this 上下文中，方便 onLeave 使用
                }
            }
        },
        onLeave: function(retval) {
            // onLeave 在 so 库加载和初始化完成后执行
            if (this.library_name && !is_jni_hooked) {
                console.log(`[*] 目标库 ${this.library_name} 加载完成，开始在其内部搜索 JNI 函数...`);
                
                // 在刚刚加载的这个库内部，或者全局，再次尝试查找 RegisterNatives
                // 优先在库内部查找，如果找不到再尝试全局查找
                const lib = Process.findModuleByName(this.library_name);
                let registerNatives_addr = lib.findExportByName("RegisterNatives");

                if (!registerNatives_addr) {
                    // 如果在库的导出函数中找不到，就搜索其内部所有符号 (可能未导出)
                    const symbols = lib.enumerateSymbols();
                    for (let i = 0; i < symbols.length; i++) {
                        const symbol = symbols[i];
                        if (symbol.name.includes("JNI") && symbol.name.includes("RegisterNatives")) {
                            console.log(`[+] 在 ${this.library_name} 内部找到可能的 RegisterNatives 符号: ${symbol.name}`);
                            registerNatives_addr = symbol.address;
                            break;
                        }
                    }
                }

                if (registerNatives_addr) {
                    console.log(`[*] 成功在 ${this.library_name} 中定位到 RegisterNatives 地址: ${registerNatives_addr}`);
                    hook_register_natives(registerNatives_addr); // 开始 Hook
                    is_jni_hooked = true; // 标记为已 Hook，防止重复操作
                } else {
                    console.log(`[!] 未在 ${this.library_name} 中找到 RegisterNatives。`);
                }
            }
        }
    });
} else {
    console.log("[!] 无法定位 android_dlopen_ext，此策略失败。");
}

// 将我们之前的 JNI Hook 逻辑封装成一个函数
function hook_register_natives(address) {
    Interceptor.attach(address, {
        onEnter: function(args) {
            const env = Java.vm.getEnv();
            const jclass = args[1];
            const methods_ptr = args[2];
            const method_count = args[3].toInt32();
            let class_name;
            try {
                class_name = env.getClassName(jclass);
            } catch (e) { return; }

            if (!filterKeywords.some(k => class_name.toLowerCase().includes(k))) return;

            const methods = [];
            for (let i = 0; i < method_count; i++) {
                const p = methods_ptr.add(i * Process.pointerSize * 3);
                const name_p = p.readPointer();
                const sig_p = p.add(Process.pointerSize).readPointer();
                const fn_p = p.add(Process.pointerSize * 2).readPointer();
                methods.push({
                    name: name_p.isNull() ? "N/A" : name_p.readCString(),
                    sig: sig_p.isNull() ? "N/A" : sig_p.readCString(),
                    addr: fn_p
                });
            }

            Java.scheduleOnMainThread(() => {
                console.log(`\n[+] JNI RegisterNatives in ${class_name} (${method_count} methods)`);
                console.log("------------------------------------------------------------");
                methods.forEach(m => {
                    const mod = Process.findModuleByAddress(m.addr);
                    let mod_info = "N/A";
                    if (mod) mod_info = `${mod.name} (base: ${mod.base}, offset: ${m.addr.sub(mod.base)})`;
                    console.log(`\t- ${m.name}${m.sig}`);
                    console.log(`\t  @ ${m.addr} -> ${mod_info}`);
                });
                console.log("------------------------------------------------------------\n");
            });
        }
    });
}